
/***  Documentation

    Mushpup Base Class
    Last Update: $Date$
    Author: Tom at klenwell@gmail.com

    DESCRIPTION
        The basic mushpup class that follows the facade pattern.
      
    USAGE
        <script src="../js/external/paj/sha1.js"></script>
        <script src="../js/external/parseUri.js"></script>
        <script src="../js/external/jquery_plugins/jquery.curvycorners.min.js"></script>
        <script src="../js/mushpup/mushpup.js"></script>
        
        $(document).ready(function(){
            var Mushpup = new MushpupBase('main');
        });
        
    
    NOTES
        
            
    REFERENCE
    
    
***/

function MushpupBase(parent_id) {

    this.version     = '2.1';
    this.Log_        = [];
    this.UI          = new MushpupUI();
    this.sha         = {};
    this.is_loaded   = 0;
    this.parent_id   = parent_id;
    this.pre_tab     = 'form';
    this.WarningList = [];
    
    // hash display limits (in seconds)
    this.display_hash_for   = 20;
    this.fade_hash_in       = 5;
    this.reset_hash_in      = 60;
    
    // modifier lists
    this.ValidModList = [ '@', 'A', '*', '+', 'a', '!' ];
    this.MutexModList = [ '@', 'A', '*' ];
    
    // maps
    this.paj_b64_map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    this.mushpup_map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789tl';
    this.alpha_map   = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzAaBbCcDdEeFf';
    this.number_map  = '0123456789012345678901234567890123456789012345678901234567890123';
    this.symbol_map  = '!?@#$%^&*()_+-={}|[]!?@#$%^&*()_+-={}|[]!?@#$%^&*()_+-={}|[]!?@#';
    
    // tab maps
    this.TabDict = {
        // id, label, view key
        'mp_tab_form' : [ 'Password', 'mp_view_form' ],
        'mp_tab_use'  : [ 'How to Use', 'mp_view_use' ],
        'mp_tab_safe' : [ 'Is This Safe?', 'mp_view_safe' ],
        'mp_tab_wiki' : [ 'Wiki', 'mp_view_wiki' ],
        'mp_tab_code' : [ 'Code', 'mp_view_code' ]
    };
    
    this.TabMap = {
        'mp_view_form' : 'mp_tab_form',
          'form'       : 'mp_tab_form',
        'mp_view_use'  : 'mp_tab_use',
          'how_to_use' : 'mp_tab_use',
          'how_use'    : 'mp_tab_use',
          'use'        : 'mp_tab_use',
        'mp_view_safe' : 'mp_tab_safe',
          'safe'       : 'mp_tab_safe',
        'mp_view_wiki' : 'mp_tab_wiki',
          'wiki'       : 'mp_tab_wiki',
        'mp_view_code' : 'mp_tab_code',
          'code'       : 'mp_tab_code'
    };
    
    this.constructor = function() {
        //this.UI.set_ctrlc_callback(this.UI.callback_test);
        this.is_loaded = 1;
        
        // get current
        this.UrlDict = this.parse_url(document.location.href);

        if ( this.UrlDict.QueryDict['tab'] !== undefined ) {
            this.pre_tab = this.UrlDict.QueryDict['tab'];
        }
        
        this.render();
    };
    
    this.render = function() {
        this.UI.render(this);
    };
    
    
    // input filtering (this is not currently necessary)
    this.filter_input = function(s) {
        return s;
    };
    
    // mushpup hashing methods
    this.hash_v2 =function(locus, pocus) {
        
        // parse locus
        var LocusList = this.parse_locus(locus);
        var domain = LocusList[0];
        var user = LocusList[1];
        var ModList = LocusList[2];
        
        // reconstruct locus
        var valid_locus = domain;
        if ( user.length > 0 ) { valid_locus += '/' + user; }
        
        // get hash
        var hash = this.basic_hash(valid_locus, pocus);
        
        // apply modifiers
        if ( ModList.length > 0 ) {
            hash = this.apply_hash_modifiers(hash, ModList);
        }
            
        return hash;        
    };
    
    this.basic_hash = function(locus, pocus) {
        return this.hash_v1(locus + pocus);
    };
    
    this.hash_v1 = function(s) {
        var hash = this.sha.b64(s);
        hash = this.normalize_hash(hash);
        return hash.substr(0,24);
    };
    
    this.normalize_hash = function(hash) {
        hash = hash.replace(/\+/g, 't');
        hash = hash.replace(/\//g, 'l');
        return hash;
    };
    
    this.apply_hash_modifiers = function(hash, ModList) {
        
        // exclusive modifiers
        if ( ModList.indexOf('@') != -1 ) {       // alphanum (i.e. no mod)
            return hash;
        }
        else if ( ModList.indexOf('A') != -1 ) {  // alpha only
            return this.remap(hash, 'alpha');
        }
        else if ( ModList.indexOf('#') != -1 ) {  // num only
            return this.remap(hash, 'number');
        }
        else if ( ModList.indexOf('*') != -1 ) {  // one of each
            ModList = ['a', '+', '!'];
        }

        // non-exclusive modifiers
        if ( ModList.indexOf('a') != -1 ) {         // at least 1 alpha
            var h5  = this.remap(hash[5],  'alpha');
            var h11 = this.remap(hash[11], 'alpha');
            var h18 = this.remap(hash[18], 'alpha');
            hash = hash.slice(0,5) + h5 + hash.slice(6,11) + h11 +
                   hash.slice(12,18) + h18 + hash.slice(19);
        }
        if ( ModList.indexOf('+') != -1 ) {         // at least 1 number
            var h3  = this.remap(hash[3],  'number');
            var h13 = this.remap(hash[13], 'number');
            var h19 = this.remap(hash[19], 'number');
            hash = hash.slice(0,3) + h3 + hash.slice(4,13) + h13 +
                   hash.slice(14,19) + h19 + hash.slice(20);
        }
        if ( ModList.indexOf('!') != -1 ) {         // at least 1 symbol
            var h4  = this.remap(hash[4],  'symbol');
            var h12 = this.remap(hash[12], 'symbol');
            var h20 = this.remap(hash[20], 'symbol');
            hash = hash.slice(0,4) + h4 + hash.slice(5,12) + h12 +
                   hash.slice(13,20) + h20 + hash.slice(21);
        }
        
        return hash;        
    };
    
    this.hash = this.hash_v1;
    
    // locus parser
    this.parse_locus = function(locus) {
        var domain = '';
        var user = '';
        var mods = '';
        var ModList = [];
        
        if ( locus[0] == '/' ) { locus = locus.slice(1); }
        
        var LocusSplit = locus.split('/');
        domain = LocusSplit[0];
        user = ( LocusSplit.length > 1 ) ? LocusSplit[1] : '';
        mods = ( LocusSplit.length > 2 ) ? LocusSplit[2] : '';
        
        if ( mods.length > 0 ) { ModList = this.parse_mods(mods); }
        
        return [ domain, user, ModList ];
    };
    
    this.parse_mods = function(mods) {
        /*
            returns list of opts or empty list
            
            splits string into list then validates list.  if list is invalid
            returns empty string and raises an alarm
        */
        var ModList = mods.split('');
        
        // valid mod list
        for ( var i in ModList ) {
            if (ModList.hasOwnProperty(i))
            {
                if ( this.ValidModList.indexOf(ModList[i]) == -1 ) {
                    this.modifier_warning('invalid', mods, ModList[i]);
                    return [];
                }
            
                if ( this.MutexModList.indexOf(ModList[i]) != -1 &&
                   ModList.length > 1 )
                {
                this.modifier_warning('conflict', mods, ModList[i]);
                return [];
                }
            }
        }
        
        // return mods
        return ModList;    
    };
    
    // warnings
    this.modifier_warning = function(type, modstring, mod) {
        var MsgLines = [];
        MsgLines.push('Invalid modifier string "' + modstring + '" : ');
        
        if ( type == 'invalid' ) {
            MsgLines.push('modifier "' + mod + '" not recognized');
        }
        else if ( type == 'conflict' ) {
            MsgLines.push('modifier "' + mod + '" must be used alone');
        }
            
        MsgLines.push('modifiers will be ignored');

        this.WarningList.push(MsgLines.join('\n'));
    };
    
    this.input_warning = function(type, alert_) {
        var m = '';
        
        if ( type == 'blank_locus' ) {
            m = 'Warning: you did not enter a site';
        }
        else if ( type == 'blank_pocus' ) {
            m = 'Warning: you did not enter a secret word';
        }
        
        this.WarningList.push(m);
        if ( alert_ ) { alert(m); }
    };
    
    // general hashing methods
    this.sha = {
        'b64' : function(s) { return b64_sha1(s); },
        'hex' : function(s) { return hex_sha1(s); }   
    };
    
    this.remap = function(s, map) {
        
        var remapped_s = '';    // return
        
        var map_map = {
            'mushpup'   : this.mushpup_map,
            'alpha'     : this.alpha_map,
            'number'    : this.number_map,
            'symbol'    : this.symbol_map
        };
        
        // create dicts
        var RemapMap = map_map[map];
        var MushpupMap = this.map_to_dict(this.mushpup_map);
        var SymbolMap = this.map_to_dict(this.symbol_map);
        
        
        // remap each character (c) of string
        for ( var i in s ) {
            if ( s.hasOwnProperty(i) ) {
                var c = String(s[i]);
                var BaseMap = MushpupMap;
                
                // set base map
                if ( this.mushpup_map.indexOf(c) != -1 ) {
                    BaseMap = MushpupMap;
                }
                else if ( this.symbol_map.indexOf(c) != -1 ) {
                    BaseMap = SymbolMap;
                }
                else {
                    c = this.mushpup_map[i % 64];
                }
                
                // convert
                remapped_s = remapped_s + String(RemapMap[BaseMap[c]]);
            } 
        }
        
        return remapped_s;
    };
    
    this.map_to_dict = function(Map) {
        var MapDict = {};
        for ( var i in Map ) {
            if ( Map.hasOwnProperty(i) ) {
                if ( MapDict[Map[i]] === undefined ) {
                    MapDict[Map[i]] = i;
                }
            }
        }
        return MapDict;
    };
    
    
    // url parsing methods
    this.parse_url = function(url) {
        
        var UriDict = parseUri(url);
        var PathSplit = UriDict['path'].split('/');
        var PathList = [];
        
        for ( var i in PathSplit ) {
            if ( PathSplit.hasOwnProperty(i) ) {
                if ( PathSplit[i].length > 0 ) {
                    PathList.push(PathSplit[i]);
                }
            }
        }

        var UrlDict = {
            'raw'           : UriDict['source'],
            'domain'        : UriDict['host'],
            'path'          : UriDict['path'],
            'PathList'      : PathList,
            'anchor'        : UriDict['anchor'],
            'query'         : UriDict['query'],
            'QueryDict'     : UriDict['queryKey'],
            'ModifierList'  : [],
            'url'           : UriDict['protocol'] + '://' + UriDict['host'] + UriDict['path']
        };
        
        return UrlDict;
    };
    
    this._parse_uri = function(url) {
        var UriDict = parseUri(url);
        return UriDict;
    };
    
    // utility methods
    this.ltrim = function(s) { return s.replace(/^\s+/, ''); };
    this.rtrim = function(s) { return s.replace(/\s+$/, ''); };   
    this.trim  = function(s) { return s.replace(/^\s+|\s+$/g, ''); };
    
    // call constructor
    this.constructor();
}


function MushpupUI() {
    
    this.version    = '1.0';
    this.ui_id      = 'mushpup_ui';
    this.skip_key   = '__^skip$__';
    this.hash_is_visible = 0;
    
    this.Mp = null;             // mushpup object
    
    // hash display limits (in seconds)
    this.display_hash_for   = 60;
    this.fade_hash_in       = 60;
    this.reset_hash_in      = 120;
    
    // display timeouts
    this.reset_after_t      = false;
    this.reset_hash_panel_t = false;
    this.close_hash_panel_t = false;
    
    // key codes
    this.kc_ctrl    = 17;
    this.kc_c       = 67;
    
    // tab settings
    this.TabDict = {};

    
    // global UI methods
    this.render = function(MushpupObj) {
        /*
          Assemble and return complete Mushpup UI Element as jQuery object.
        */
        var $MushpupEl = $('<div />').attr('id', this.ui_id);
        
        // create mushpup obj
        this.Mp = MushpupObj;
        this.TabDict = MushpupObj.TabDict;
        this.display_hash_for   = MushpupObj.display_hash_for;
        this.fade_hash_in       = MushpupObj.fade_hash_in;
        this.reset_hash_in      = MushpupObj.reset_hash_in;
        var mpid = MushpupObj.parent_id;
        
        
        // append to document
        if ( mpid ) {
            $('#'+mpid).append($MushpupEl);
        }
        
        // build panels
        $MushpupEl.append(this.build_tab_panel());
        $MushpupEl.append(this.build_banner_panel());
        $MushpupEl.append(this.build_view_panel());
        $MushpupEl.append(this.build_hash_panel());
        $MushpupEl.append(this.build_footer_panel());
        
        // round corners
        $('.mp_tab_link').corner({
            tl: { radius: 10 },
            tr: { radius: 10 },
            bl: { radius: false },
            br: { radius: false },
            antiAlias: true,
            autoPad: true,
            validTags: ["a"]
        });
        $('#mp_banner').corner({
            tl: { radius: 16 },
            tr: { radius: 16 },
            bl: { radius: false },
            br: { radius: false },
            antiAlias: true,
            autoPad: true,
            validTags: ["div"]
        });
        $('.mp_view').corner({
            tl: { radius: 10 },
            tr: { radius: 10 },
            bl: { radius: 10 },
            br: { radius: 10 },
            antiAlias: true,
            autoPad: true,
            validTags: ["div"]
        });
        $('#hash_panel > .child').corner({
            tl: { radius: 10 },
            tr: { radius: 10 },
            bl: { radius: 10 },
            br: { radius: 10 },
            antiAlias: true,
            autoPad: true,
            validTags: ["div"]
        });
        $('#mp_pitch_block').corner({
            tl: { radius: 0 },
            tr: { radius: 0 },
            bl: { radius: 10 },
            br: { radius: 10 },
            antiAlias: true,
            autoPad: true,
            validTags: ["div"]
        });
        
        // hide confirm input field and set to preset tab
        var tab_button_id = '#' + this.Mp.TabMap[this.Mp.pre_tab];
        $(tab_button_id).find('a').click();
        
        return $MushpupEl;
    };
    
    this.form_validates = function(locus, pocus, pocus2)
    {        
        if ( pocus2 != this.skip_key && pocus != pocus2 )
		{
            alert("Your mushpup secret words didn't match.\nCarefully re-enter them.");
            $('#pocus').val('').focus();
            $('#pocus2').val('');
			return 0;
		}
        
		if ( locus === "" ) {
            this.Mp.input_warning('blank_locus', 1);
        }
        if ( pocus === "" ) {
            this.Mp.input_warning('blank_pocus', 1);
        }

        return 1;
    };
    
    // view reset methods
    this.refresh = function() {};
    this.reset_after = function(secs) {
        //console.log('reset_after', secs);
        var $this = this;
        
        if ( this.reset_after_t ) {
            clearTimeout(this.reset_after_t);
        }
        
        this.reset_after_t = setTimeout( function() {
            //console.log('reset_after now');
            $this.reset_form_panel();
            $this.reset_hash_panel_after(0);
        }, secs * 1000);
    };
    this.reset_hash_panel_after = function(secs) {
        //console.log('reset_hash_panel_after', secs);
        var $this = this;
        
        if ( this.reset_hash_panel_t ) {
            clearTimeout(this.reset_hash_panel_t);
        }
        
        this.reset_hash_panel_t = setTimeout( function() {
            //console.log('reset_hash_panel_after now');
            $('#mp_ruler_hsh').html('');
            $this.close_hash_panel_after(0);
        }, secs * 1000);
    };
    this.close_hash_panel_after = function(secs) {
        //console.log('close_hash_panel_after', secs);
        var $this = this;
        
        if ( this.close_hash_panel_t ) {
            clearTimeout(this.close_hash_panel_t);
        }
        
        this.close_hash_panel_t = setTimeout( function() {
            //console.log('close_hash_panel_after fade', $this.fade_hash_in);
            $this.hash_is_visible = 0;
            $('#hash_panel').fadeOut($this.fade_hash_in * 1000);
        }, secs * 1000);
    };
    
    // render view
    this.build_view_panel = function() {
        var $ViewPanel = $('<div id="mp_view_panel" />');
        
        jQuery.each( this.ViewDict, function( key, view ) {
            var $ChildDiv = $('<div class="mp_view" />').attr('id', key)
                .html(view).hide();
            //$ChildDiv.corners('10px');
            $ViewPanel.append($ChildDiv);
        });
        
        return $ViewPanel;
    };
    
    this.render_view_panel = function(key) {
        $('.mp_view').hide();
        //$ChildDiv = $('<div class="mp_view" />').attr('id', key)
        //    .html(this.ViewDict[key]);
        //$('#mp_view_panel').html($ChildDiv);
        
        // show hash if form view panel and visible
        if ( key == 'mp_view_form' && this.hash_is_visible ) {
            $('#hash_panel').show();
        }
        else {
            $('#hash_panel').hide();
        }
        
        $('#'+key).show();
    };
    
    
    // form panel methods
    this.build_form_panel = function()
    {
        // build container and header
        var $FormPanel = $('<div id="mp_view_form" />')
            .html("<h3>password musher</h3>");
            
        $FormPanel.append(this.build_locus_block());
        $FormPanel.append(this.build_pocus_block());
        $FormPanel.append(this.build_pocus2_block());
        $FormPanel.append(this.build_form_buttons());
        
        return $FormPanel;
    };
    
    this.build_locus_block = function()
    {
        var advice_link = "http://mushpup.org/wiki/wikka.php?wakka=HomePage";
        var advice_copy = "<b>domain/userid</b> (no 'http://' in front) is "+
            "the recommended format. use only the basic domain name "+
            "(<b>yahoo.com</b> rather than <b>http://www.yahoo.com/login/</b>). "+
            "for more info, <a href=\"" + advice_link + "\">click here</a>.";
        
        var $LocusBlock = $('<p id="locus_block" />');
        var $LocusLabel = $('<label for="locus" />').text('site (e.g. yahoo.com/myuserid)');
        var $LocusInput = $('<input id="locus" name="locus" type="text" />')
            .attr('tabindex', '1').attr('size', '40').val('');
        var $LocusAdvice = $('<div id="advice_block" />').html(advice_copy);
        
        $LocusBlock.append($LocusLabel).append($LocusInput)
            .append($LocusAdvice);
        return $LocusBlock;
    };
    
    this.build_pocus_block = function()
    {
        var $PocusBlock = $('<p id="pocus_block" />');
        var $PocusLabel = $('<label for="pocus" />').text('mushpup secret word');
        var $PocusInput = $('<input id="pocus" name="pocus" type="password" />')
            .attr('tabindex', '2').attr('size', '20').val('');
        
        $PocusBlock.append($PocusLabel).append($PocusInput);
        return $PocusBlock;
    };
    
    this.build_pocus2_block = function()
    {
        // hard settings
        var span_copy = '(recommended if you are registering a password for first time at a site)';
        
        // callbacks
        var show_pocus_match = function() {
            $('#pocus2').val('');
            $('#pocus_confirm').hide();
            $('#pocus2_block').show().focus();
        };
        
        var $PocusConfirmBlock = $('<div id="pocus_confirm_block" />');
        
        // Confirm Link
        var $PocusConfirm = $('<p id="pocus_confirm" />');
        var $PocusLink = $('<a>').text('click here to add confirm field').click(show_pocus_match);
        var $PocusSpan = $('<span>').text(span_copy);
        $PocusConfirm.append($PocusLink).append($PocusSpan);
        
        // Confirm Input Field      
        var $PocusBlock2 = $('<p id="pocus2_block" />').hide();
        var $PocusLabel2 = $('<label for="pocus2" />').text('re-enter to confirm');
        var $PocusInput2 = $('<input id="pocus2" name="pocus2" type="password" size="20" />').
            val(this.skip_key);
        var $PocusHideLink = $('<a id="pocus_hide_link">').text('hide')
            .click(this.hide_pocus_match);
        $PocusBlock2.append($PocusLabel2).append($PocusInput2)
            .append($PocusHideLink);
            
        $PocusConfirmBlock.append($PocusConfirm).append($PocusBlock2);
        return $PocusConfirmBlock;
    };
    
    this.form_action = function()
    {
        var locus  = jQuery.trim($('#locus').val());
        var pocus  = jQuery.trim($('#pocus').val());
        var pocus2 = jQuery.trim($('#pocus2').val());
        
        if ( this.form_validates(locus, pocus, pocus2) ) {
            this.update_hash(this.Mp.hash_v2(locus, pocus));
        }
    };
    
    this.build_form_buttons = function()
    {
        // callbacks
        var $this = this;
        var mushpup_it = function() {
            $this.form_action();            
        };
        var reset_ = function() {
            $this.reset_after(0);
        };
        
        var $ButtonBlock = $('<div id="form_button_block" />');
        var $MushButton = $('<button id="get_password_bx" />').text('get password')
            .click(mushpup_it);
        var $ResetButton = $('<button id="reset_bx" />').text('reset')
            .click(reset_);
        
        $ButtonBlock.append($MushButton).append($ResetButton);
        return $ButtonBlock;
    };
    
    this.hide_pocus_match = function() {
        $('#pocus2_block').hide();
        $('#pocus_confirm').show();
        $('#pocus2').val(this.skip_key);
    };
    
    this.reset_form_panel = function() {
        /*
            To reset form, reset input fields and hide confirm field
        */
        $('#locus').val('');
        $('#pocus').val('');
        this.hide_pocus_match();
    };
    
    // hash panel methods
    this.MP_RULER_HASH_ID = 'mp_ruler_hsh';
    this.top_ruler = '>...5....0....5....0...>';
    this.init_hash = '........................';
    this.bot_ruler = '<...0....5....0....5...<';
    this.rdot = '&bull;';
    
    this.build_hash_panel = function(){
        var $HashPanel = $('<div id="hash_panel" />');
        //var $HashInner = $('<div class="child" />').corners('10px');
        var $HashInner = $('<div class="child" />');
        
        // build header
        var $HashHeader = $('<div class="header" />');
        var _p = 'your password is somewhere in here (copy and paste as needed):';
        var $CloseBx = $('<a class="close_bx" />').text('[x]').click(function() {
            $('#hash_panel').slideUp(750); });
        var $HashWarnings = $('<div id="hash_warning" />');
        var $Prompt  = $('<h4 />').text(_p);
        $HashHeader.append($CloseBx).append($HashWarnings).append($Prompt);
        
        // build ruler
        var $MpRuler = $('<div id="mp_ruler_block" />');
        var $RulerTop = $('<div id="mp_ruler_top" />')
            .append(this._build_ruler('mp_ruler_top', this.top_ruler));
        var $RulerHsh = $('<div />').attr('id', this.MP_RULER_HASH_ID)
            .append(this._build_ruler('mp_ruler_hsh', this.init_hash));
        var $RulerBot = $('<div id="mp_ruler_bot" />')
            .append(this._build_ruler('mp_ruler_bot', this.bot_ruler));
        $MpRuler.append($RulerTop).append($RulerHsh).append($RulerBot);
        
        $HashPanel.append($HashInner.append($HashHeader).append($MpRuler));
        $HashPanel.hide();
        return $HashPanel;
    };
    
    
    this._build_ruler = function(id_, tpl) {
	    var pre = id_ + '_';
	    var $Parent = $('<div class="mp_ruler" />');
	    var $Span1 =  $('<span class="span1" />');
	    var $Span2 =  $('<span class="span2" />');
	    var $Span3 =  $('<span class="span3" />');

	    var i=0;
	    while ( i < tpl.length )
	    {            
	        var c = tpl.charAt(i);
	        var cid = pre + String(i+1);
	        var o = i+1;
	        var c_ = 'strong';
	        
	        if ( jQuery.inArray(c, ['<','>']) != -1 ) {
	            c = '&#' + c.charCodeAt(0) + ';';
	        }
	        else if ( c == '.' ) {
	            if ( o % 5 === 0 ) {
	                c = String(o % 10);
	            }
	            else {
	                c_ = 'lite';
	                c = this.rdot;
	            }
	        }

	        var $c = $('<span id="'+cid+'" class="'+c_+'" />').html(c);

	        if ( o <= 8 ) { $Span1.append($c); }
	        else if ( o > 8 && o <= 16 ) { $Span2.append($c); }
	        else { $Span3.append($c); }
	        i = i+1;
	    }
	    
	    return $Parent.append($Span1).append($Span2).append($Span3);
	};
    
    this.update_hash = function(new_hash) {
        this.hash_is_visible = 1;
        
        // build warning
        $('#hash_warning').html('');
        if ( this.Mp.WarningList.length > 0 )
        {
            var $WarningUl = $('<ul class="warning_list" />');
            while ( this.Mp.WarningList.length > 0 ) {
                var w = this.Mp.WarningList.pop().replace(/\n/g, '<br />');
                var $WarningLi = $('<li>').html( w );
                $WarningUl.append($WarningLi);
            }
            $('#hash_warning').html($WarningUl);
        }
        
        var $NewRulerHsh = this._build_ruler(this.MP_RULER_HASH_ID, new_hash);
        $('#mp_ruler_hsh').html($NewRulerHsh);
        
        // show for a limited time then hide hash then reset
        $('#hash_panel').show();
        this.close_hash_panel_after(this.display_hash_for);
        this.reset_after(this.reset_hash_in);        
    };
    
    this.reveal_hash_help_block = function() {};
    this.hide_hash_help_block = function() {};
    
    // banner panel methods
    this.build_banner_panel = function() {
        var html_ = '<h1>mushpup ' + this.Mp.version + '</h1>';
        var subtext = "Turn your favorite password into as many unique, "+
            "secure passwords as you want. And don't bother to remember "+
            "any of them.";
        //var $Banner = $('<div id="mp_banner" />').html(html_).corners('top 16px');
        var $Banner = $('<div id="mp_banner" />').html(html_);
        var $SubBanner = $('<h4 class="subbanner">').text(subtext);
        $Banner.append($SubBanner);
        return $Banner;
    };
    
    this.style_banner = function() {
        var banner_css = {
            'padding':'16px',
            'background-color':'#6699CC',
            'color':'#fff', 'font-size':'36px',
            'background-image':"url('../img/mushpup_logo.png')",
            'background-position':'right center',
            'background-repeat':'no-repeat'
        };
        var subbanner_css = {};
        $('#mp_banner').css(banner_css);
        $('h4.subbanner').css(subbanner_css);
        return true;
    };
    
    // tab panel methods
    this.build_tab_panel = function() {
        
        var $TabPanel = $('<div id="mp_tab_panel" />');
        var $TabUl = $('<ul />');
        
        // append each tab based on TabDict
        var $this = this;
        jQuery.each( this.TabDict, function(k,v) {
            $TabUl.append( $this.build_tab(k, v[0], v[1]) );
        });
        
        return $TabPanel.append($TabUl);
    };
    
    this.build_tab = function( id, label, view ) {
        
        var $this = this;
        var callback_ = function() {
            $this.activate_tab(id);
            $this.render_view_panel(view);
            // hide hash panel
        };
        
        var $Tab = $('<li class="mp_tab" />').attr('id', id);
        //var $TabLink = $('<a class="mp_tab_link" />').text(label)
        //    .corners('10px top').click( callback_ );
        var $TabLink = $('<a class="mp_tab_link" />').text(label).click( callback_ );
        
        return $Tab.append($TabLink);
    };
    
    this.activate_tab = function( id ) {
        $('.mp_tab_link').removeClass('active');
        $('#'+id).find('a').addClass('active');
    };
    
    
    // footer
    this.build_footer_panel = function() {
        
        var year_ = new Date().getFullYear();
        var pw_link = 'http://code.google.com/p/mushpup/wiki/Password';
        var ff_link = 'http://www.spreadfirefox.com/?q=affiliates&amp;id=151890&amp;t=85';
        var ff_img  = 'http://sfx-images.mozilla.org/affiliates/Buttons/80x15/firefox_80x15.png';
        var pitch_text = 'Whenever you have to register at a new site, you '+
            'can use the form above to get your new password. And then come '+
            'back here to get it next time you need it. For more information '+
            'on how to pick a good password and why this is probably more '+
            'safe and secure than what you\'re currently doing, '+
            '<a href="' + pw_link + '" onclick="window.open(this.href,\'_blank\');return false;">click here</a>.';
        
        var $FooterPanel = $('<div id="mp_footer_panel" />');        
        var $PitchBlock = $('<div id="mp_pitch_block" />').html(pitch_text);
            //corners('10px bottom');
        
        var $FooterBlock = $('<div id="mp_footer_block" />');
        var $FooterLeft = $('<div class="left" />').
            html('<a href="http://mushpup.org/">mushpup.org</a>');
        var $FooterRight = $('<div class="right" />').append(
                $('<a />').attr('href', ff_link).click(
                    function() { window.open(this.href,'_blank'); return false; }
                ).append(
                    $('<img />').attr('src', ff_img) ) );
        var $FooterCenter = $('<div class="center" />').html('some rights reserved, &copy; ' + String(year_));
        $FooterBlock.append($FooterLeft).append($FooterRight).append($FooterCenter);
        
        $FooterPanel.append($PitchBlock).append($FooterBlock);
        return $FooterPanel;
    };
 
 
    // event handlers   
    this.set_ctrlc_callback = function(callback_fx) {
        $(document).unbind('keyup');
        $(document).bind( 'keyup', function(e) {
            var pressed = (window.event) ? event : e;
            var key_code = (window.event) ? event.keyCode : e.keyCode;
            if ( pressed.ctrlKey && key_code == 67) { callback_fx(); }
        });
    };
    
    // test functions
    this.load_test = function() { return this.version; };
    this.callback_test = function() { alert('callback test'); };
    
    /* View Dict
        Items in this dict represent views for the main view panel. For
        information on multiline strings, see:
        http://stackoverflow.com/questions/451954/multiline-javascript-text    
    */
    this.ViewDict = {
        'mp_view_form' : this.build_form_panel(),
        
        'mp_view_use'  : ' '+
'<h3>A Quick Intro to Using Mushpup</h3> '+
'<!-- Step 1: Mushpup Password -->'+
'<div class="step" id="step1">'+
'<h4>Step 1: Pick Your Mushpup Secret Word</h4>'+
'This will be your master password. It doesn\'t have to be super long or super '+
'complex.  But it should be distinctive and <b>use both numbers and letters</b>. '+
'Don\'t write it down.  Don\'t utter it.  Commit it to memory.'+
'</div>'+
''+
'<!-- Step 2: Mushpup Number -->'+
'<div class="step" id="step2">'+
'<h4>Step 2: Pick Your Mushpup Number</h4>'+
'This should be a number between 8 and 16.  This will be the length of '+
'your passwords.  But don\'t worry too much about length -- you won\'t have '+
'to remember any passwords!  You only have to remember your <b>mushpup '+
'secret word</b>. '+
'</div>'+
''+
'<!-- Step 3: Mushpup Side -->'+
'<div class="step" id="step3">'+
'<h4>Step 3: Pick Your Side</h4>'+
'Lefty (front-sider)?  Or righty (back-sider)?  Or maybe straight down '+
'the middle?  Pick your side and be loyal to it.  (You\'ll see why this '+
'matters once you get your password.) <em>Now you should be ready for the '+
'form under the password tab!</em> '+
'</div>'+
''+
'<!-- Step 4: Get Password -->'+
'<div class="step" id="step4">'+
'<h4>Get Your Password</h4>'+
'Now enter the site (format <tt>domain.com/username</tt>) and your secret '+
'word in the mushpup form.  Then, from the display string of letter, start '+
'at the side you picked and highlight the number of characters in the number '+
'you picked.  <b>That is your password for that user on that site.</b> '+
'</div>',
        
        'mp_view_safe' : ''+
'<div class="intro">'+
'Is this safe?  Not completely.  But likely safer and more convenient than '+
'the method you\'re currently using.  Read on to find out why: '+
'</div> '+
''+
'<div class="faq">'+
'<h4>Why is this more secure than just creating my own passwords?</h4>'+
'<p>The main reason is mushpup creates many '+
'non-obvious, hard-to-crack, site-specific passwords from one master password. '+
'The form at right uses the <a href="http://en.wikipedia.org/wiki/SHA_hash_functions"> '+
'SHA-1 algorithm</a> to create a (virtually) unique '+
'one-way hash code out of your mushpup password and the site.  It looks random, '+
'but the same inputs (password and site) will always produce the same hash.  '+
'It\'s one-way, so it is all but impossible to guess your password from the hash.  Which '+
'means if your password is stolen from one site, it can\'t be used at another one.</p> '+
'<p>For more info on why this is more secure than simply memorizing your own passwords, '+
'<a href="http://mushpup.org/wiki/Passwords">click here.</a></p> '+
'</div>'+
''+
'<div class="faq">'+
'<h4>How do I know you\'re not going to store my password and log in to my account or '+
'sell it to leet ninja hackers?</h4>'+
'<p>We would never do such a thing!  But even if we wanted to, we couldn\'t.  This '+
'page uses only client-side javascript.  That means your computer does all the computations '+
'and nothing (after the page initially loads) is sent to the server (our computer). '+
'(You can check the source code to verify this &mdash; or have a competent programmer check it for you.)</p> '+
'</div>'+
''+
'<div class="faq">'+
'<h4>Is this guaranteed to be utterly, totally secure?</h4>'+
'<p>No way!  It comes with no guarantees and we don\'t recommend using it for websites that involve '+
'really sensitive personal information (like your bank or primary e-mail website.)  But for less important '+
'sites (like secondary email accounts or online forums), it\'s safe enough.  Mostly, we hope '+
'it will serve as a just good-enough convenience and spur you to start thinking '+
'more carefully about ways to lock down your really sensitive personal information.</p> '+
'<p><a href="http://mushpup.org/wiki/Passwords">click here</a> for ways you can make mushpup even more secure</p> '+
'</div>'+
''+
'<div class="faq">'+
'<h4>What does the form do?</h4>'+
'<p>It simply uses <a href="http://mushpup.org/wiki/MushPupSha24">this script</a> to create a hash that, '+
'following the simple steps listed here, you can use as your password for the '+
'website or context you\'ve specified.  Give it a try by signing up for <a href="http://mushpup.org/wiki/wikka.php?wakka=UserSettings">the mushpup wiki</a> and '+
'you\'ll see how easy it is!</p>'+
'</div>',
        
        'mp_view_wiki' : ''+
'For more information on mushpup, how it works and how to use it, visit the '+
'<a href="http://mushpup.org/wiki/" '+
'onclick="window.open(this.href,\'_blank\');return false;">mushpup wiki</a>. '+
'',
        
        'mp_view_code' : ''+
'The source code for mushpup, along with additional information on its '+
'design and development, is available at the '+
'<a href="http://code.google.com/p/mushpup/" '+
'onclick="window.open(this.href,\'_blank\'); return false;">mushpup code site</a> '+
'hosted by google code. '+
''
    };

}
